﻿using System;
using System.Data.Objects;
using System.Web;
using System.Web.Mvc;
using Prepare.Domain.Entities;

namespace Prepare.Domain
{
	public class UnitOfWork : IUnitOfWork
	{
		private const string HttpContextKey = "Prepare.Repository.HttpContextKey";
		private static Func<ObjectContext> _contextDelegate;
		private static readonly Object _lockObject = new object();

		/// <summary>
		/// Store the context with the instance of the UnitOfWork
		/// </summary>
		public ObjectContext Context { get; private set; }

		/// <summary>
		/// Retrieve the current Unit of Work from the HttpContext, creating a new one if needed.
		/// </summary>
		public static IUnitOfWork Current
		{
			get
			{
				IUnitOfWork uow = GetUnitOfWork();
				if (uow == null)
				{
					uow = DependencyResolver.Current.GetService<IUnitOfWork>();
					//! uow = ObjectFactory.GetInstance<IUnitOfWork>();
					if (HttpContext.Current != null)
					{
						HttpContext.Current.Items[HttpContextKey] = uow;
					}
				}
				return uow;
			}
		}

		#region Lifetime                          - - - - - - - - - - - - - - - - - - - - - - - - -

		/// <summary>
		/// Constructor that stores the object context with the UnitOfWork object
		/// </summary>
		/// <param name="context"></param>
		public UnitOfWork()
		{
			lock (_lockObject)
			{
				Context = _contextDelegate();
				Context.ContextOptions.LazyLoadingEnabled = true;
			}
		}

		/// <summary>
		/// Commit the changes made to the context in the UnitOfWork
		/// </summary>
		public static void Commit()
		{
			IUnitOfWork uow = GetUnitOfWork();

			if (uow != null && uow.Context != null)
			{
				uow.Context.SaveChanges();
			}
		}

		void IDisposable.Dispose()
		{
			if (Context != null)
			{
				Context.Dispose();
				Context = null;
			}
			GC.SuppressFinalize(this);
		}
		
		/// <summary>
		/// Dispose of the object
		/// </summary>
		public static void Dispose()
		{
			if (Current != null)
			{
				Current.Dispose();
			}
		}

		#endregion Lifetime

		#region Static Methods                    - - - - - - - - - - - - - - - - - - - - - - - - -

		/// <summary>
		/// Initialize the Dependency Container and store the context
		/// delegate for the duration of the request
		/// </summary>
		/// <param name="contextDelegate"></param>
		public static void Initialize(Func<ObjectContext> contextDelegate)
		{
			// tell concrete factory which EF model we want to use
			_contextDelegate = contextDelegate;
		}

		/// <summary>
		/// Return the current context cast to a specific type
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <returns></returns>
		public static T GetContext<T>() where T : ObjectContext
		{
			return (T)Current;

			T context = null;
			var uow = GetUnitOfWork();
			if (uow != null && uow.Context != null)
			{
				context = (T)uow.Context;
			}
			return context;
		}

		/// <summary>
		/// Retrieves the instance of a typed repository from the DI container.
		/// For entities without a manager, this allows basic access to its repository.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <returns></returns>
		public static IRepository<T> GetRepository<T>() where T : class
		{
			return DependencyResolver.Current.GetService<IRepository<T>>();
			//! return ObjectFactory.GetInstance<IRepository<T>>();
		}

		/// <summary>
		/// Return the UnitOfWork from the HttpContext
		/// </summary>
		/// <returns></returns>
		private static IUnitOfWork GetUnitOfWork()
		{
			IUnitOfWork uow = null;
			if (HttpContext.Current != null && HttpContext.Current.Items.Contains(HttpContextKey))
			{
				uow = (IUnitOfWork)HttpContext.Current.Items[HttpContextKey];
			}
			return uow;
		}

		#endregion Static Methods
	}
}
